﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Threading;

namespace NanoMvvm.Collections
{
    public class ObservableList<T> : Collection<T>, IObservableList<T>
    {
        [NonSerialized]
        private readonly SynchronizationContext synchronizationContext;

        public ObservableList()
            : this(NanoMvvmFramework.Instance.Synchronization.Main)
        {
        }

        public ObservableList(SynchronizationContext synchronizationContext)
        {
            this.synchronizationContext = synchronizationContext;
        }

        [field: NonSerialized]
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        protected override void ClearItems()
        {
            if (synchronizationContext != null)
            {
                synchronizationContext.Send(state => ClearItemsImpl(), null);
            }
            else
            {
                ClearItemsImpl();
            }

            // PropertyChanged events are automatically marshalled
            // to the UI thread by the wpf system
            OnPropertyChanged(new PropertyChangedEventArgs("Count"));
            OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        }

        protected override void InsertItem(int index, T item)
        {
            if (synchronizationContext != null)
            {
                synchronizationContext.Send(state => InsertItemImpl(index, item), null);
            }
            else
            {
                InsertItemImpl(index, item);
            }

            OnPropertyChanged(new PropertyChangedEventArgs("Count"));
            OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        }

        protected override void RemoveItem(int index)
        {
            if (synchronizationContext != null)
            {
                synchronizationContext.Send(state => RemoveItemImpl(index), null);
            }
            else
            {
                RemoveItemImpl(index);
            }

            OnPropertyChanged(new PropertyChangedEventArgs("Count"));
            OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        }

        protected override void SetItem(int index, T item)
        {
            if (synchronizationContext != null)
            {
                synchronizationContext.Send(state => SetItemImpl(index, item), null);
            }
            else
            {
                SetItemImpl(index, item);
            }

            OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        }

        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            var handler = CollectionChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        private void ClearItemsImpl()
        {
            base.ClearItems();
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        private void InsertItemImpl(int index, T item)
        {
            base.InsertItem(index, item);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
        }

        private void RemoveItemImpl(int index)
        {
            T item = this[index];
            base.RemoveItem(index);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
        }

        private void SetItemImpl(int index, T item)
        {
            T old = this[index];
            base.SetItem(index, item);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, old, index));
        }
    }
}